Skip to content

feat: configure API retry backoff (#370) - #1095

Merged
kevincodex1 merged 3 commits into
Twigpine:mainfrom
hicap-oss:issue-370
May 25, 2026
Merged

kevincodex1 merged 3 commits into
Twigpine:mainfrom
hicap-oss:issue-370

Conversation

@jatmn

@jatmn jatmn commented May 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds OpenClaude-branded retry controls for retryable API failures:
    • OPENCLAUDE_MAX_RETRIES
    • OPENCLAUDE_RETRY_DELAY_MS
  • Replaces the old CLAUDE_CODE_MAX_RETRIES configuration name in the retry path.
  • Allows OPENCLAUDE_MAX_RETRIES=0 to disable retries after the initial request.
  • Keeps provider Retry-After headers authoritative when they are present.
  • Documents the new settings in .env.example and docs/advanced-setup.md.
  • Adds focused tests for retry defaults, invalid values, caps, zero retries, configured delay, and Retry-After precedence.

Why

Fixes #370.

Some OpenAI-compatible providers, including providers with per-second rate limits, can return transient 429 responses without a useful Retry-After header. Before this change, OpenClaude had retry behavior, but users could not tune the fallback backoff delay for providers that omit Retry-After, and the exposed retry-count env var still used old Claude Code branding.

This gives users a clear OpenClaude configuration surface for retry count and fallback retry delay while preserving the existing behavior by default.

User Impact

Users can now tune retry behavior without changing code:

OPENCLAUDE_MAX_RETRIES=10
OPENCLAUDE_RETRY_DELAY_MS=500

OPENCLAUDE_MAX_RETRIES=0 disables retries after the initial request.

When an API response includes Retry-After, OpenClaude still honors that server-provided delay instead of the configured fallback delay.

Provider Paths

This affects the shared API retry path used for retryable API failures, including OpenAI-compatible providers. The most relevant path for issue #370 is providers that return retryable 429s without Retry-After.

Validation

  • bun install
    • Passed
    • Checked 714 installs across 516 packages with no changes
  • bun run build
    • Passed
    • Built dist/cli.mjs
    • Built dist/sdk.mjs
    • External list validation passed
    • SDK type declarations were in sync
    • Build printed existing warnings about optional external entries not in package.json, but exited successfully
  • bun run smoke
    • Passed
    • Rebuilt successfully
    • node dist/cli.mjs --version returned 0.9.2 (OpenClaude)
  • bun test src/services/api/withRetry.test.ts
    • Passed in earlier focused validation for this branch
    • Note: this focused test can hit an unrelated source-checkout import issue around optional feature-gated command modules when run in isolation. I did not add unrelated test scaffolding changes to this PR.

Notes

  • No unrelated model-effort changes are included.
  • Branch is based on upstream main.
  • No changes were made to persistent unattended retry mode.

Add OpenClaude-branded retry controls for retryable API failures.

- Replace the old CLAUDE_CODE_MAX_RETRIES config with OPENCLAUDE_MAX_RETRIES

- Allow OPENCLAUDE_MAX_RETRIES=0 to disable retries after the initial request

- Cap retry attempts at 100 and invalid values fall back to the default of 10

- Add OPENCLAUDE_RETRY_DELAY_MS to configure the exponential backoff base for APIs that omit Retry-After

- Keep Retry-After precedence over configured retry delay

- Document both settings in .env.example and advanced setup docs

- Add focused retry configuration tests for defaults, invalid values, caps, zero retries, configured delay, and Retry-After precedence

Validation:

- bun test src/services/api/withRetry.test.ts

- bun run build
@jatmn jatmn self-assigned this May 9, 2026

@techbrewboss techbrewboss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused retry-config slice. The new OPENCLAUDE_RETRY_DELAY_MS path behaves as described in the branch tests, and bun run build passes locally.

I found one compatibility issue with the env var rename that should be handled before merge.

Validation I ran locally:

  • bun run build passed
  • bun test src/services/api/withRetry.test.ts currently fails on the known isolated-import issue: Cannot find module './commands/fork/index.js' from 'src/commands.ts'. After that first import failure, the remaining retry assertions in this file pass.

Comment thread src/services/api/withRetry.ts Outdated
return parseInt(process.env.CLAUDE_CODE_MAX_RETRIES, 10)
}
return DEFAULT_MAX_RETRIES
return validateRetryAttemptsEnvVar(process.env.OPENCLAUDE_MAX_RETRIES)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This drops support for the previously documented CLAUDE_CODE_MAX_RETRIES setting without any fallback. Existing users who already set CLAUDE_CODE_MAX_RETRIES=0 or tuned it down for CI will silently go back to the default 10 retries after upgrading, which is a behavioral regression in the same retry path this PR is changing. Can we keep a compatibility fallback when OPENCLAUDE_MAX_RETRIES is unset, preferably with the new env var taking precedence and maybe a debug/deprecation message for the legacy name?

Add compatibility fallback from CLAUDE_CODE_MAX_RETRIES when OPENCLAUDE_MAX_RETRIES is unset.

Document the deprecated fallback and cover precedence behavior in retry configuration tests.
@jatmn
jatmn requested a review from techbrewboss May 10, 2026 00:44
techbrewboss
techbrewboss previously approved these changes May 10, 2026

@techbrewboss techbrewboss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed current head f41c1972. The compatibility issue I raised earlier is fixed: OPENCLAUDE_MAX_RETRIES takes precedence, and CLAUDE_CODE_MAX_RETRIES is still honored as a deprecated fallback when the new env var is unset. The docs and tests cover that behavior.

Validation I ran locally:

  • CLAUDE_CODE_MAX_RETRIES=0 with no OPENCLAUDE_MAX_RETRIES returns 0 from getDefaultMaxRetries()
  • bun test src/services/api/withRetry.test.ts passes: 28/28
  • git diff upstream/main...HEAD --check passes
  • bun run build passes

I do not see a remaining blocker.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Targeted review of current head f41c197, focused on the retry configuration behavior for #370.

Verdict: Approve-ready

What I checked:

  • OPENCLAUDE_MAX_RETRIES is the new primary setting.
  • CLAUDE_CODE_MAX_RETRIES remains a deprecated fallback when the OpenClaude setting is unset, so existing users do not silently lose their retry config.
  • OPENCLAUDE_MAX_RETRIES=0 correctly disables retries after the initial request.
  • Retry-After remains authoritative over configured fallback delay.
  • Ran bun test ./src/services/api/withRetry.test.ts: 28/28 passing.

I do not see a blocker on current head.

@jatmn

jatmn commented May 13, 2026

Copy link
Copy Markdown
Collaborator Author

@kevincodex1

@Vasanthdev2004

Copy link
Copy Markdown
Collaborator

Blockers

None.

Non-Blocking

None.

Looks Good

  • Configurable API retry backoff
  • 153 additions, 6 deletions — focused feature
  • Already has maintainer approvals

Verdict: Approve — clean retry backoff feature.

Vasanthdev2004
Vasanthdev2004 previously approved these changes May 16, 2026

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean retry backoff feature.

gnanam1990
gnanam1990 previously approved these changes May 16, 2026

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — clean, well-scoped retry-config slice. 🎉

I verified the safety properties: bounded (100-retry / 60s caps), invalid values fall back to defaults, OPENCLAUDE_MAX_RETRIES=0 correctly disables retries, server-provided Retry-After stays authoritative over the configured fallback, and legacy CLAUDE_CODE_MAX_RETRIES is honored as a documented deprecated fallback so existing users don't silently lose config. No new network surface in third-party paths, no red flags, and the tests cover the matrix and pass on current head. Maps cleanly to #370. Thanks!

@jatmn
jatmn dismissed stale reviews from gnanam1990, Vasanthdev2004, and techbrewboss via 2d01cbe May 24, 2026 06:29
@kevincodex1
kevincodex1 merged commit d02c10b into Twigpine:main May 25, 2026
2 checks passed
@jatmn
jatmn deleted the issue-370 branch May 25, 2026 17:10
discopops pushed a commit to discopops/openclaude that referenced this pull request May 28, 2026
* feat: configure API retry backoff

Add OpenClaude-branded retry controls for retryable API failures.

- Replace the old CLAUDE_CODE_MAX_RETRIES config with OPENCLAUDE_MAX_RETRIES

- Allow OPENCLAUDE_MAX_RETRIES=0 to disable retries after the initial request

- Cap retry attempts at 100 and invalid values fall back to the default of 10

- Add OPENCLAUDE_RETRY_DELAY_MS to configure the exponential backoff base for APIs that omit Retry-After

- Keep Retry-After precedence over configured retry delay

- Document both settings in .env.example and advanced setup docs

- Add focused retry configuration tests for defaults, invalid values, caps, zero retries, configured delay, and Retry-After precedence

Validation:

- bun test src/services/api/withRetry.test.ts

- bun run build

* Honor legacy max retries env var

Add compatibility fallback from CLAUDE_CODE_MAX_RETRIES when OPENCLAUDE_MAX_RETRIES is unset.

Document the deprecated fallback and cover precedence behavior in retry configuration tests.

---------

Co-authored-by: JATMN <12479882+jatmn@users.noreply.github.com>
Gravirei added a commit to Gravirei/openclaude that referenced this pull request May 28, 2026
- fix(autocompact): retry circuit breaker after cooldown (Twigpine#1375)
- fix(provider): require API key input when adding OpenGateway (Twigpine#1384)
- fix(provider): allow remote Ollama without OPENAI_API_KEY (Twigpine#952)
- fix(codex-stream): recover tool args delivered only via done events (Twigpine#1262)
- fix: route MiniMax compacting through Anthropic-compatible API (Twigpine#1154)
- fix(thinking): disable thinking for unsupported Ollama models (Twigpine#1376)
- feat(agents): set active session agent from agents menu (Twigpine#1349)
- fix(repl): show permission prompts while draft input is present (Twigpine#1393)
- fix(model): include profile models in descriptor picker (Twigpine#1361)
- Improve warning notice formatting (Twigpine#1415)
- fix(codex): allow credential storage fallback (Twigpine#1347)
- fix(attribution): make git attribution opt-in by default (Twigpine#1335)
- fix(agent): allow custom model overrides (Twigpine#1337)
- feat(query): robust multi-lingual and structural continuation nudge (Twigpine#1280)
- fix(watchers): debounce skills and settings reload bursts (Twigpine#1370)
- feat: configure API retry backoff (Twigpine#370) (Twigpine#1095)
- chore(main): release 0.15.0 (Twigpine#1325)
- ci: retrigger CodeQL after action download outage (Twigpine#1374)
- Fix launcher heap setup for long sessions (Twigpine#1242)
hotmanxp added a commit to hotmanxp/openclaude that referenced this pull request Jun 7, 2026
Backport of upstream d02c10b 'feat: configure API retry backoff (Twigpine#370)
(Twigpine#1095)' with fork-rebranded env var names (OPENCLAUDE_* → OPENCC_*).

The previous tier 2 sync (939802d) dropped the 9 'retry configuration'
tests in withRetry.test.ts because the upstream code added
OPENCLAUDE_MAX_RETRIES / OPENCLAUDE_RETRY_DELAY_MS env var support that
the fork intentionally didn't port. This commit ports the feature
properly under the fork's preferred OPENCC_ naming.

What OPENCC_MAX_RETRIES does:
- Replaces legacy CLAUDE_CODE_MAX_RETRIES as the primary config
- Allow 0 to disable retries after the initial request
- Cap at 100, fall back to default (10) for invalid values
- CLAUDE_CODE_MAX_RETRIES is still honored as a deprecated fallback
  with a logForDebugging deprecation notice (per fork policy: keep
  CLAUDE_CODE_* env vars as user-facing API)

What OPENCC_RETRY_DELAY_MS does:
- Configures base retry delay (ms) for APIs that don't send Retry-After
- Capped at 60000, falls back to default (500) for invalid values
- Retry-After header takes precedence over the configured delay

Files changed (4):
- src/services/api/withRetry.ts: add validateRetryAttemptsEnvVar helper,
  import validateBoundedIntEnvVar, refactor getDefaultMaxRetries to read
  OPENCC_MAX_RETRIES first then fall back to CLAUDE_CODE_MAX_RETRIES,
  add getDefaultRetryDelayMs() function, wire baseDelayMs into
  getRetryDelay exponential backoff
- src/services/api/withRetry.test.ts: add 3 new env keys to envKeys
  array, re-add the 9 'retry configuration' tests with OPENCC_ names,
  rename existing OPENCLAUDE_RETRY_DELAY_MS → OPENCC_RETRY_DELAY_MS in
  the 'OpenAI-compatible retry classification' block (8 occurrences)
- .env.example: add OPENCC_MAX_RETRIES + OPENCC_RETRY_DELAY_MS
  documentation in the OPTIONAL TUNING section
- docs/advanced-setup.md: add 2 new env var table rows

Verification:
  typecheck:  0 errors
  build:      Built v0.16.1 → dist/cli.mjs
  bun test:   2544 pass / 0 fail / 34 skip (full suite, +12 vs 939802d)
  naming:     no OPENCLAUDE_(MAX_RETRIES|RETRY_DELAY) leak
hotmanxp added a commit to hotmanxp/openclaude that referenced this pull request Jun 11, 2026
Backport of upstream d02c10b 'feat: configure API retry backoff (Twigpine#370)
(Twigpine#1095)' with fork-rebranded env var names (OPENCLAUDE_* → OPENCC_*).

The previous tier 2 sync (939802d) dropped the 9 'retry configuration'
tests in withRetry.test.ts because the upstream code added
OPENCLAUDE_MAX_RETRIES / OPENCLAUDE_RETRY_DELAY_MS env var support that
the fork intentionally didn't port. This commit ports the feature
properly under the fork's preferred OPENCC_ naming.

What OPENCC_MAX_RETRIES does:
- Replaces legacy CLAUDE_CODE_MAX_RETRIES as the primary config
- Allow 0 to disable retries after the initial request
- Cap at 100, fall back to default (10) for invalid values
- CLAUDE_CODE_MAX_RETRIES is still honored as a deprecated fallback
  with a logForDebugging deprecation notice (per fork policy: keep
  CLAUDE_CODE_* env vars as user-facing API)

What OPENCC_RETRY_DELAY_MS does:
- Configures base retry delay (ms) for APIs that don't send Retry-After
- Capped at 60000, falls back to default (500) for invalid values
- Retry-After header takes precedence over the configured delay

Files changed (4):
- src/services/api/withRetry.ts: add validateRetryAttemptsEnvVar helper,
  import validateBoundedIntEnvVar, refactor getDefaultMaxRetries to read
  OPENCC_MAX_RETRIES first then fall back to CLAUDE_CODE_MAX_RETRIES,
  add getDefaultRetryDelayMs() function, wire baseDelayMs into
  getRetryDelay exponential backoff
- src/services/api/withRetry.test.ts: add 3 new env keys to envKeys
  array, re-add the 9 'retry configuration' tests with OPENCC_ names,
  rename existing OPENCLAUDE_RETRY_DELAY_MS → OPENCC_RETRY_DELAY_MS in
  the 'OpenAI-compatible retry classification' block (8 occurrences)
- .env.example: add OPENCC_MAX_RETRIES + OPENCC_RETRY_DELAY_MS
  documentation in the OPTIONAL TUNING section
- docs/advanced-setup.md: add 2 new env var table rows

Verification:
  typecheck:  0 errors
  build:      Built v0.16.1 → dist/cli.mjs
  bun test:   2544 pass / 0 fail / 34 skip (full suite, +12 vs 939802d)
  naming:     no OPENCLAUDE_(MAX_RETRIES|RETRY_DELAY) leak
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retry Failed Request

5 participants